package lox


class Interpreter private constructor(
    private var globals: Environment,
    private var environment: Environment = globals,
    private val locals: MutableMap<Expr, Int> = mutableMapOf()
) : Expr.Visitor<Any?>, Stmt.Visitor<Unit> {

    constructor() : this(Environment()) {
        with(globals) {
            define("clock", object : LoxCallable {

                @Suppress("ALWAYS_RETURN_NONNULL")
                override fun call(interpreter: Interpreter, arguments: List<Any?>): Any? =
                    System.currentTimeMillis() / 1000.0

                override fun arity(): Int = 0

                override fun toString(): String = "<native function>"

            })
        }
    }

    private fun stringify(any: Any?): String = when (any) {
        null -> "nil"

        is Double -> any.toString().let {
            if (it.endsWith(".0")) it.substring(0, it.length - 2) else it
        }

        else -> any.toString()
    }

    fun interpret(statements: List<Stmt>) = try {
        for (statement in statements) {
            execute(statement)
        }
    } catch (error: RuntimeError) {
        Lox.runtimeError(error)
    }

    private fun execute(stmt: Stmt) = stmt.accept(this)

    private fun evaluate(expr: Expr): Any? = expr.accept(this)

    override fun visitAssignExpr(expr: Expr.Assign): Any? = evaluate(expr.value).also {
        when (val distance = locals[expr]) {
            null -> globals.assign(expr.name, it)
            else -> environment.assignAt(distance, expr.name, it)
        }
    }

    override fun visitBinaryExpr(expr: Expr.Binary): Any? {
        val left = evaluate(expr.left)
        val right = evaluate(expr.right)

        return when (expr.operator.type) {
            TokenType.MINUS -> {
                checkNumberOperand(expr.operator, left, right)
                left as Double - right as Double
            }

            TokenType.SLASH -> {
                checkNumberOperand(expr.operator, left, right)
                left as Double / right as Double
            }

            TokenType.STAR -> {
                checkNumberOperand(expr.operator, left, right)
                left as Double * right as Double
            }

            TokenType.PLUS -> {
                when {
                    left is Double && right is Double -> left + right
                    left is String && right is String -> left + right
                    else -> throw RuntimeError(expr.operator, "Operands must be two numbers or strings.")
                }
            }

            TokenType.GREATER -> {
                checkNumberOperand(expr.operator, left, right)
                left as Double > right as Double
            }

            TokenType.GREATER_EQUAL -> {
                checkNumberOperand(expr.operator, left, right)
                left as Double >= right as Double
            }

            TokenType.LESS -> {
                checkNumberOperand(expr.operator, left, right)
                (left as Double) < (right as Double)
            }

            TokenType.LESS_EQUAL -> {
                checkNumberOperand(expr.operator, left, right)
                left as Double <= right as Double
            }

            TokenType.EQUAL_EQUAL -> {
                isEqual(left, right)
            }

            TokenType.BANG_EQUAL -> {
                !isEqual(left, right)
            }

            else -> {
                null
            }
        }
    }

    override fun visitCallExpr(expr: Expr.Call): Any? {
        val callee = evaluate(expr.callee)

        val arguments = expr.arguments.map { evaluate(it) }

        val function = when (callee) {
            is LoxCallable -> callee
            else -> throw RuntimeError(expr.paren, "Can only call functions and classes.")
        }

        if (arguments.size != function.arity()) throw RuntimeError(
            expr.paren, "Expected ${function.arity()} arguments but got ${arguments.size}."
        )

        return function.call(this, arguments)
    }

    override fun visitGetExpr(expr: Expr.Get): Any? = when (val obj = evaluate(expr.obj)) {
        is LoxInstance -> obj[expr.name]
        else -> throw RuntimeError(expr.name, "Only instance has properties.")
    }


    private fun isEqual(left: Any?, right: Any?): Boolean = when {
        left == null && right == null -> true
        left == null -> false

        else -> left == right
    }

    override fun visitGroupingExpr(expr: Expr.Grouping): Any? = evaluate(expr.expression)

    override fun visitLiteralExpr(expr: Expr.Literal): Any? = expr.value
    override fun visitLogicalExpr(expr: Expr.Logical): Any? {
        return evaluate(expr.left).run {
            val left = this

            when {
                expr.operator.type == TokenType.OR && isTruthy(left) -> left
                expr.operator.type == TokenType.AND && !isTruthy(left) -> left
                else -> evaluate(expr.right)
            }
        }

    }

    override fun visitSetExpr(expr: Expr.Set): Any? = when (val obj = evaluate(expr.obj)) {
        is LoxInstance -> evaluate(expr.value).also { obj[expr.name] = it }
        else -> throw RuntimeError(expr.name, "Only instances have fields.")
    }

    override fun visitSuperExpr(expr: Expr.Super): Any? {
        val distance = locals[expr]!!
        val superclass = environment.getAt(distance, "super") as LoxClass
        val obj = environment.getAt(distance - 1, "this") as LoxInstance

        return when (val method = superclass.findMethod(expr.method.lexeme)) {
            null -> throw RuntimeError(expr.method, "undefined property ${expr.method.lexeme}.")
            else -> method.bind(obj)
        }
    }

    override fun visitThisExpr(expr: Expr.This): Any? = lookUpVariable(expr.keyword, expr)

    override fun visitUnaryExpr(expr: Expr.Unary): Any? {
        val right = evaluate(expr.right)

        return when (expr.operator.type) {
            TokenType.MINUS -> {
                checkNumberOperand(expr.operator, right)
                -(right as Double)
            }

            TokenType.BANG -> !isTruthy(right)
            else -> null
        }
    }

    override fun visitVariableExpr(expr: Expr.Variable): Any? = lookUpVariable(expr.name, expr)

    private fun lookUpVariable(name: Token, expr: Expr): Any? = when (val distance = locals[expr]) {
        null -> globals[name]
        else -> environment.getAt(distance, name.lexeme)
    }


    private fun checkNumberOperand(operator: Token, expr: Any?) {
        if (expr is Double) return
        throw RuntimeError(operator, "Operand must be a number.")
    }

    private fun checkNumberOperand(operator: Token, vararg exprs: Any?) {
        if (exprs.all { it is Double }) return
        throw RuntimeError(operator, "Operands must be numbers.")
    }

    private fun isTruthy(expr: Any?): Boolean = when (expr) {
        null -> false
        is Boolean -> expr
        else -> true
    }

    override fun visitBlockStmt(stmt: Stmt.Block) {
        executeBlock(stmt.statements, Environment(environment))
    }

    override fun visitClassStmt(stmt: Stmt.Class) {

        val superclass = stmt.superclass?.run {
            evaluate(this)
        }?.run {
            if (this !is LoxClass) {
                throw RuntimeError(stmt.superclass.name, "Superclass must be a class.")
            }
            this
        }

        environment.define(stmt.name.lexeme, null)

        if (stmt.superclass != null) {
            environment = Environment(environment)
            environment.define("super", superclass)
        }

        val methods = mutableMapOf<String, LoxFunction>()

        stmt.methods.forEach { method ->
            methods[method.name.lexeme] = LoxFunction(method, environment, method.name.lexeme == "init")
        }

        val klass = LoxClass(stmt.name.lexeme, superclass, methods)

        if (superclass != null) {
            environment = environment.enclosing!!
        }

        environment.assign(stmt.name, klass)
    }

    internal fun executeBlock(statements: List<Stmt>, environment: Environment) {
        val previous = this.environment

        try {
            this.environment = environment

            for (statement in statements) {
                execute(statement)
            }
        } finally {
            this.environment = previous
        }
    }

    override fun visitExpressionStmt(stmt: Stmt.Expression) {
        evaluate(stmt.expression)
    }

    override fun visitFunctionStmt(stmt: Stmt.Function): Unit = LoxFunction(stmt, environment, false).run {
        environment.define(stmt.name.lexeme, this)
    }

    override fun visitIfStmt(stmt: Stmt.If) = when {
        isTruthy(evaluate(stmt.condition)) -> execute(stmt.thenBranch)
        stmt.elseBranch != null -> execute(stmt.elseBranch)
        else -> {}
    }

    override fun visitPrintStmt(stmt: Stmt.Print) {
        evaluate(stmt.expression).run { println(stringify(this)) }
    }

    // 使用抛出异常的方式实现提前返回
    // 天才般的设想
    override fun visitReturnStmt(stmt: Stmt.Return) {
        throw Return(stmt.value?.run { evaluate(this) })
    }

    override fun visitWhileStmt(stmt: Stmt.While) {
        while (isTruthy(evaluate(stmt.condition))) {
            execute(stmt.body)
        }
    }

    override fun visitVarStmt(stmt: Stmt.Var) {
        stmt.initializer?.run { evaluate(this) }.run { environment.define(stmt.name.lexeme, this) }
    }

    fun resolve(expr: Expr, depth: Int) {
        locals[expr] = depth
    }

}